﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.ComponentModel.Composition.Primitives;
using System.Reflection;

namespace FluentMef
{
    public interface IImportExpression<T>
    {
        IImportExpression<T> ConstructWith<TParam>();
        IImportExpression<T> ConstructWith<TParam1, TParam2>();
        IImportExpression<T> ConstructWith<TParam1, TParam2, TParam3>();
        IImportExpression<T> ConstructWith(params Type[] types);
        IImportExpression<T> Import<TType>(Expression<Func<T, TType>> expression);
        IImportExpression<T> Import<TType>(Expression<Func<T, TType>> expression, string contractName);
    }

    sealed class ImportExpression<T> : ComposablePartDefinition, IComposablePartDefinitionBuilder, IImportExpression<T>
    {
        Func<Dictionary<FluentImportDefinition, object>, T> _constructor;
        Action<IComposablePartDefinitionBuilder> _updateBuilder;
        ExportExpression<T> _export;
        readonly List<FluentImportDefinition> _imports = new List<FluentImportDefinition>();
        List<ConstructorFluentImportDefinition> _constructParams = new List<ConstructorFluentImportDefinition>();

        internal ImportExpression(ExportExpression<T> export, Action<IComposablePartDefinitionBuilder> updateBuilder)
        {
            _updateBuilder = updateBuilder;
            _export = export;
            var init = export.ExportValueInitializer;
            _constructor = (imports) => init();
        }

        public IImportExpression<T> ConstructWith<TParam>()
        {
            return ConstructWith(typeof(TParam));
        }

        public IImportExpression<T> ConstructWith<TParam1, TParam2>()
        {
            return ConstructWith(typeof(TParam1), typeof(TParam2));
        }

        public IImportExpression<T> ConstructWith<TParam1, TParam2, TParam3>()
        {
            return ConstructWith(typeof(TParam1), typeof(TParam2), typeof(TParam3));
        }

        public IImportExpression<T> ConstructWith(params Type[] types)
        {
            if (types == null || types.Length == 0) throw new ArgumentNullException("types");
            if (_export.ExportIsInstance) throw new InvalidOperationException();

            //TODO: handle contract names to deal with this...
            if (types.Length != types.Distinct().Count()) throw new ArgumentException("Duplicate types as arguments.");

            foreach (var t in types)
            {
                var def = new ConstructorFluentImportDefinition(t, ContractNameServices.GetTypeIdentity(t));

                _imports.Add(def);
                _constructParams.Add(def);
            }

            _constructor = ConstructWithParams;

            _updateBuilder(this);
            return this;
        }

        public IImportExpression<T> Import<TType>(Expression<Func<T, TType>> expression)
        {
            return Import<TType>(expression, ContractNameServices.GetTypeIdentity(typeof(TType)));
        }

        public IImportExpression<T> Import<TType>(Expression<Func<T, TType>> expression, string contractName)
        {
            if(string.IsNullOrWhiteSpace(contractName)) throw new ArgumentNullException("contractName");
            if(expression.Body.NodeType != ExpressionType.MemberAccess) throw new ArgumentException("expression must be ExpressionType.MemberAccess", "expression");

            var body = (MemberExpression) expression.Body;

            _imports.Add(new PropertyFluentImportDefinition((PropertyInfo)body.Member, contractName));
            
            _updateBuilder(this);
            return this;
        }

        private T ConstructWithParams(Dictionary<FluentImportDefinition, object> imports)
        {
            var paramValues = new List<object>();

            foreach (var p in _constructParams)
            {
                paramValues.Add(((Export)imports[p]).Value);
            }

            return (T)Activator.CreateInstance(_export.ExportType, paramValues.ToArray());
        }

        ComposablePartDefinition IComposablePartDefinitionBuilder.BuildDefinition()
        {
            return this;
        }

        public override ComposablePart CreatePart()
        {
            return new FluentComposablePart<T>(_constructor, this);
        }

        public override IEnumerable<ExportDefinition> ExportDefinitions
        {
            get { return _export.ExportDefinitions; }
        }

        public override IEnumerable<ImportDefinition> ImportDefinitions
        {
            get { return _imports; }
        }
    }
}
